#pragma once

#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/epoll.h>
#include "SpinLock.h"
#include "BufBlock.h"
#include "BufMgr.h"

// 接收buffer不发送，发送buffer不接收,确保多线程情况下不同时发送或同时接收
// Send buffer 加锁

// enum class ELockType
// {
//     None,
//     Recv,
//     Send
//     // Both
// };

// template <typename CLock = CFakeLock, ELockType LockType = ELockType::None>
template <typename CLock = CFakeLock>
class CBuffer
{
public:
    CBuffer() { m_iDataLen = 0; }
    ~CBuffer() { Destroy(); }

    void Destroy()
    {
        BufBlockNode *pHead = nullptr;
        CBufMgr *pBufMgr = CBufMgr::GetInstance();

        while (0 < m_lstData.GetSize())
        {
            pHead = m_lstData.GetHeader();

            if (nullptr == pHead)
                return;

            m_lstData.Remove(pHead);
            pBufMgr->FreeBlock(pHead);
        }
    }

public:
    int Read(void *pDest, int iLen)
    {
        IoResult res;
        int iRead = 0;
        BufBlockNode *pHead = m_lstData.GetHeader();

        if ((nullptr == pDest) || (iLen <= 0))
            return -1;

        if (nullptr == pHead)
            return -1;

        while (iLen > 0)
        {
            res = pHead->Read((uint8_t *)pDest + iRead, iLen);
            iRead += res.nLen;
            iLen -= res.nLen;
            m_iDataLen -= res.nLen;

            if (!res.bRes)
                break;

            if (res.bEnd)
            {
                CBufMgr *pBufMgr = CBufMgr::GetInstance();
                BufBlockNode *pNode = pHead->Next();

                if (nullptr == pNode)
                    return -1;

                m_lstData.Remove(pHead);
                pBufMgr->FreeBlock(pHead);
                pHead = pNode;
            }
        }

        return iRead;
    }

    int Write(void *pSrc, int iLen)
    {
        IoResult res;
        int iWrote = 0;
        CBufMgr *pBufMgr = nullptr;
        BufBlockNode *pTail = m_lstData.GetTailer();

        if ((nullptr == pSrc) || (iLen <= 0))
            return -1;

        if ((nullptr == pTail) || ((nullptr != pTail) && (pTail->nWrite == pTail->nEnd)))
        {
            pBufMgr = CBufMgr::GetInstance();
            pTail = pBufMgr->GetBlock();

            if (nullptr == pTail)
                return -1;

            m_lstData.PushBack(pTail);
        }

        while (iLen > 0)
        {
            res = pTail->Write((uint8_t *)pSrc + iWrote, iLen);
            iWrote += res.nLen;
            iLen -= res.nLen;
            m_iDataLen += res.nLen;

            if (!res.bRes)
                break;

            if (res.bEnd)
            {
                pBufMgr = CBufMgr::GetInstance();
                pTail = pBufMgr->GetBlock();

                if (nullptr == pTail)
                    return -1;

                m_lstData.PushBack(pTail);
            }
        }

        return iWrote;
    }

    // equivalent to Write
    int Recv()
    {
        IoResult res;
        int iRecv = 0;
        CBufMgr *pBufMgr = nullptr;
        BufBlockNode *pTail = m_lstData.GetTailer();

        if ((nullptr == pTail) || ((nullptr != pTail) && (pTail->nWrite == pTail->nEnd)))
        {
            pBufMgr = CBufMgr::GetInstance();
            pTail = pBufMgr->GetBlock();

            if (nullptr == pTail)
                return -1;

            m_lstData.PushBack(pTail);
        }

        while (true)
        {
            res = pTail->Recv(m_ifd);

            if (res.bErr)
                return -1;

            if (res.bEnd)
            {
                iRecv += res.nLen;
                m_iDataLen += res.nLen;

                pBufMgr = CBufMgr::GetInstance();
                pTail = pBufMgr->GetBlock();

                if (nullptr == pTail)
                    return -1;

                m_lstData.PushBack(pTail);
            }

            if (res.bRes)
            {
                iRecv += res.nLen;
                m_iDataLen += res.nLen;
                break;
            }
        }

        return iRecv;
    }

    // equivalent to Read
    int Send()
    {
        IoResult res;
        int iSent = 0;
        BufBlockNode *pHead = m_lstData.GetHeader();

        if (nullptr == pHead)
            return -1;

        m_lock.lock();

        while (m_iDataLen > 0)
        {
            res = pHead->Send(m_ifd);

            if (res.bErr)
            {
                m_lock.unlock();
                return -1;
            }

            if (res.bEnd)
            {
                iSent += res.nLen;
                m_iDataLen -= res.nLen;

                CBufMgr *pBufMgr = CBufMgr::GetInstance();
                BufBlockNode *pNode = pHead->Next();

                if (nullptr == pNode)
                {
                    m_lock.unlock();
                    return -1;
                }

                m_lstData.Remove(pHead);
                pBufMgr->FreeBlock(pHead);
                pHead = pNode;
            }

            if (res.bRes)
            {
                iSent += res.nLen;
                m_iDataLen -= res.nLen;

                break;
            }
        }

        m_lock.unlock();
        return iSent;
    }

    int Send(void *pSrc, int iLen)
    {
        int iRet = 0;
        int iSent = 0;

        if ((nullptr == pSrc) || (iLen <= 0))
            return -1;

        m_lock.lock();

        // 在没有数据时才可以直接发送
        if (0 >= m_iDataLen)
        {
            iRet = SendData(pSrc, iLen);

            if (iRet >= iLen)
            {
                m_lock.unlock();
                return iRet;
            }

            iLen -= iRet;
            iSent += iRet;
            pSrc = (uint8_t *)pSrc + iRet;
        }

        iRet = Write(pSrc, iLen);
        m_lock.unlock();

        if (-1 == iRet)
            return iRet;

        iSent += iRet;
        return iSent;
    }

    void SetSocket(int ifd)
    {
        m_ifd = ifd;
    }

    int GetDataLength()
    {
        return m_iDataLen;
    }

private:
    int SendData(void *pSrc, int iLen)
    {
        int iRet = 0;
        int iSent = 0;

        while (0 < iLen)
        {
            iRet = write(m_ifd, pSrc, iLen);

            if (0 < iRet)
            {
                iSent += iRet;

                if (iRet >= iLen)
                    break;

                pSrc = (uint8_t *)pSrc + iRet;
                iLen -= iRet;
            }
            else
            {
                if (-1 == iRet)
                {
                    if (EAGAIN == errno)
                        break;
                    else if (EINTR == errno)
                        continue;
                }

                iSent = -1;
                break;
            }
        }

        return iSent;
    }

private:
    CBufBlockList m_lstData;
    int m_iDataLen;
    int m_ifd;
    CLock m_lock;
};
